<?php


namespace app\command;


use app\service\shadowsocks\Encryptor;
use app\service\shadowsocks\ShadowsocksProtocol;
use app\service\shadowsocks\Socks5;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Workerman\Connection\AsyncTcpConnection;
use Workerman\Connection\AsyncUdpConnection;
use Workerman\Worker;

class ShadowsocksCommand extends \Symfony\Component\Console\Command\Command
{

    protected static $defaultName = 'config:socks5';
    protected static $defaultDescription = 'Socks5服务配置';

    protected function configure()
    {
        $this
            // 命令的名称 ("php console_command" 后面的部分)
            //->setName('model:create')
            // 运行 "php console_command list" 时的简短描述
            ->setDescription('Create new model')
            // 运行命令时使用 "--help" 选项时的完整命令描述
            ->setHelp('This command allow you to create models...')
            // 配置一个参数
            ->addArgument('name', InputArgument::REQUIRED, 'what\'s model you want to create ?');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        global $argv;
        //$argv[2] = 'start';
        $argv[3] = '-d';
        $LOCAL_PORT = config('shadowsocks.LOCAL_PORT');
        $PROCESS_COUNT = config('shadowsocks.PROCESS_COUNT');
        $METHOD = config('shadowsocks.METHOD');
        $PASSWORD = config('shadowsocks.PASSWORD');
        $PROTOCOL = config('shadowsocks.PROTOCOL');
        $PROTOCOL_PARAM = config('shadowsocks.PROTOCOL_PARAM');
        $SERVER = config('shadowsocks.SERVER');
        $PORT = config('shadowsocks.PORT');
        // 初始化worker，监听$LOCAL_PORT端口
        $worker = new Worker('tcp://0.0.0.0:'. $LOCAL_PORT);
        // 进程数量
        $worker->count = $PROCESS_COUNT;
        // 名称
        $worker->name = 'shadowsocks-local';
        // 当客户端连上来时
        $worker->onConnect = function($connection)use($METHOD, $PASSWORD, $PROTOCOL, $PROTOCOL_PARAM)
        {
            // 设置当前连接的状态为STAGE_INIT，初始状态
            $connection->stage = STAGE_INIT;
            // 初始化加密类
            $connection->encryptor = new Encryptor($PASSWORD, $METHOD);
            $iv = $connection->encryptor->getIV(true);
            $key = $connection->encryptor->getKey();
            // 初始化协议类
            $connection->ssprotocol = new ShadowsocksProtocol($key, $iv, $PROTOCOL, $PROTOCOL_PARAM);
        };

        // 当客户端发来消息时
        $worker->onMessage = function($connection, $buffer)use($LOCAL_PORT, $SERVER, $PORT)
        {
            // 判断当前连接的状态
            switch($connection->stage)
            {
                case STAGE_INIT:
                    //与客户端建立SOCKS5连接
                    //参见: https://www.ietf.org/rfc/rfc1928.txt
                    $connection->send("\x05\x00");
                    $connection->stage = STAGE_ADDR;
                    return;
                case STAGE_ADDR:
                    $cmd = ord($buffer[1]);
                    //仅处理客户端的TCP连接请求
                    if($cmd != CMD_CONNECT)
                    {
                        echo "unsupport cmd\n";
                        $connection->send("\x05\x07\x00\x01");
                        return $connection->close();
                    }
                    $connection->stage = STAGE_CONNECTING;
                    $buf_replies = "\x05\x00\x00\x01\x00\x00\x00\x00". pack('n', $LOCAL_PORT);
                    $connection->send($buf_replies);
                    $address = "tcp://$SERVER:$PORT";
                    $remote_connection = new AsyncTcpConnection($address);
                    $connection->opposite = $remote_connection;
                    $remote_connection->opposite = $connection;
                    // 流量控制
                    $remote_connection->onBufferFull = function($remote_connection)
                    {
                        $remote_connection->opposite->pauseRecv();
                    };
                    $remote_connection->onBufferDrain = function($remote_connection)
                    {
                        $remote_connection->opposite->resumeRecv();
                    };
                    // 远程连接发来消息时，进行解密，转发给客户端
                    $remote_connection->onMessage = function($remote_connection, $buffer)
                    {
                        $buffer = $remote_connection->opposite->encryptor->decrypt($buffer);
                        $buffer = $remote_connection->opposite->ssprotocol->ClientPostDecrypt($buffer);
                        $remote_connection->opposite->send($buffer);
                    };
                    // 远程连接断开时，则断开客户端的连接
                    $remote_connection->onClose = function($remote_connection)
                    {
                        // 关闭对端
                        $remote_connection->opposite->close();
                        $remote_connection->opposite = null;
                    };
                    // 远程连接发生错误时（一般是建立连接失败错误），关闭客户端的连接
                    $remote_connection->onError = function($remote_connection, $code, $msg)use($address)
                    {
                        echo "remote_connection $address error code:$code msg:$msg\n";
                        $remote_connection->close();
                        if($remote_connection->opposite)
                        {
                            $remote_connection->opposite->close();
                        }
                    };
                    // 流量控制
                    $connection->onBufferFull = function($connection)
                    {
                        $connection->opposite->pauseRecv();
                    };
                    $connection->onBufferDrain = function($connection)
                    {
                        $connection->opposite->resumeRecv();
                    };
                    // 当客户端发来数据时，加密数据，并发给远程服务端
                    $connection->onMessage = function($connection, $buffer)
                    {
                        $buffer = $connection->ssprotocol->ClientPreEncrypt($buffer);
                        $buffer = $connection->encryptor->encrypt($buffer);
                        $connection->opposite->send($buffer);
                    };
                    // 当客户端关闭连接时，关闭远程服务端的连接
                    $connection->onClose = function($connection)
                    {
                        $connection->opposite->close();
                        $connection->opposite = null;
                    };
                    // 当客户端连接上有错误时，关闭远程服务端连接
                    $connection->onError = function($connection, $code, $msg)
                    {
                        echo "connection err code:$code msg:$msg\n";
                        $connection->close();
                        if(isset($connection->opposite))
                        {
                            $connection->opposite->close();
                        }
                    };
                    // 执行远程连接
                    $remote_connection->connect();
                    // 改变当前连接的状态为STAGE_STREAM，即开始转发数据流
                    $connection->state = STAGE_STREAM;
                    //转发首个数据包，包含由客户端封装的目标地址，端口号等信息
                    $buffer = substr($buffer, 3);
                    $buffer = $connection->ssprotocol->ClientPreEncrypt($buffer);
                    $buffer = $connection->encryptor->encrypt($buffer);
                    $remote_connection->send($buffer);
            }
        };

        // 初始化worker，监听$PORT端口
        $line_worker = new Worker('tcp://0.0.0.0:'. $PORT);
        $line_worker->count = $PROCESS_COUNT;
        $line_worker->name = 'shadowsocks-server';

        // 当shadowsocks客户端连上来时
        $line_worker->onConnect = function($connection)use($METHOD, $PASSWORD)
        {
            // 设置当前连接的状态为STAGE_INIT，初始状态
            $connection->stage = STAGE_INIT;
            // 初始化加密类
            $connection->encryptor = new Encryptor($PASSWORD, $METHOD);
        };

        // 当shadowsocks客户端发来消息时
        $line_worker->onMessage = function($connection, $buffer)use($PROTOCOL, $PROTOCOL_PARAM)
        {
            // 判断当前连接的状态
            switch($connection->stage)
            {
                // 如果不是STAGE_STREAM，则尝试解析实际的请求地址及端口
                case STAGE_INIT:
                case STAGE_ADDR:
                    // 先解密数据
                    $buffer = $connection->encryptor->decrypt($buffer);
                    $iv = $connection->encryptor->getIV(false);
                    $key = $connection->encryptor->getKey();
                    // 初始化协议类
                    $connection->ssprotocol = new ShadowsocksProtocol($key, $iv, $PROTOCOL, $PROTOCOL_PARAM);
                    $buffer = $connection->ssprotocol->ServerPostDecrypt($buffer);
                    // 解析协议出错，则关闭连接
                    if($buffer === false) {
                        $connection->close();
                        return;
                    }

                    // 解析socket5头
                    $header_data = Socks5::parse_socket5_header($buffer);
                    // 解析头部出错，则关闭连接
                    if(!$header_data)
                    {
                        $connection->close();
                        return;
                    }
                    // 头部长度
                    $header_len = $header_data[3];
                    // 解析得到实际请求地址及端口
                    $host = $header_data[1];
                    $port = $header_data[2];
                    $address = "tcp://$host:$port";
                    if (empty($host) || empty($port)) {
                        return $connection->close();
                    }
                    // 异步建立与实际服务器的远程连接
                    $remote_connection = new AsyncTcpConnection($address);
                    $connection->opposite = $remote_connection;
                    $remote_connection->opposite = $connection;
                    // 流量控制，远程连接的发送缓冲区满，则停止读取shadowsocks客户端发来的数据
                    // 避免由于读取速度大于发送速导致发送缓冲区爆掉
                    $remote_connection->onBufferFull = function($remote_connection)
                    {
                        $remote_connection->opposite->pauseRecv();
                    };
                    // 流量控制，远程连接的发送缓冲区发送完毕后，则恢复读取shadowsocks客户端发来的数据
                    $remote_connection->onBufferDrain = function($remote_connection)
                    {
                        $remote_connection->opposite->resumeRecv();
                    };
                    // 远程连接发来消息时，进行加密，转发给shadowsocks客户端，shadowsocks客户端会解密转发给浏览器
                    $remote_connection->onMessage = function($remote_connection, $buffer)
                    {
                        $buffer = $remote_connection->opposite->ssprotocol->ServerPreEncrypt($buffer);
                        $buffer = $remote_connection->opposite->encryptor->encrypt($buffer);
                        $remote_connection->opposite->send($buffer);
                    };
                    // 远程连接断开时，则断开shadowsocks客户端的连接
                    $remote_connection->onClose = function($remote_connection)
                    {
                        // 关闭对端
                        $remote_connection->opposite->close();
                        $remote_connection->opposite = null;
                    };
                    // 远程连接发生错误时（一般是建立连接失败错误），关闭shadowsocks客户端的连接
                    $remote_connection->onError = function($remote_connection, $code, $msg)use($address)
                    {
                        echo "remote_connection $address error code:$code msg:$msg\n";
                        $remote_connection->close();
                        if(!empty($remote_connection->opposite))
                        {
                            $remote_connection->opposite->close();
                        }
                    };
                    // 流量控制，shadowsocks客户端的连接发送缓冲区满时，则停止读取远程服务端的数据
                    // 避免由于读取速度大于发送速导致发送缓冲区爆掉
                    $connection->onBufferFull = function($connection)
                    {
                        $connection->opposite->pauseRecv();
                    };
                    // 流量控制，当shadowsocks客户端的连接发送缓冲区发送完毕后，继续读取远程服务端的数据
                    $connection->onBufferDrain = function($connection)
                    {
                        $connection->opposite->resumeRecv();
                    };
                    // 当shadowsocks客户端发来数据时，解密数据，并发给远程服务端
                    $connection->onMessage = function($connection, $buffer)
                    {
                        $buffer = $connection->encryptor->decrypt($buffer);
                        $buffer = $connection->ssprotocol->ServerPostDecrypt($buffer);
                        $connection->opposite->send($buffer);
                    };
                    // 当shadowsocks客户端关闭连接时，关闭远程服务端的连接
                    $connection->onClose = function($connection)
                    {
                        $connection->opposite->close();
                        $connection->opposite = null;
                    };
                    // 当shadowsocks客户端连接上有错误时，关闭远程服务端连接
                    $connection->onError = function($connection, $code, $msg)
                    {
                        echo "connection err code:$code msg:$msg\n";
                        $connection->close();
                        if(isset($connection->opposite))
                        {
                            $connection->opposite->close();
                        }
                    };
                    // 执行远程连接
                    $remote_connection->connect();
                    // 改变当前连接的状态为STAGE_STREAM，即开始转发数据流
                    $connection->state = STAGE_STREAM;
                    // shadowsocks客户端第一次发来的数据超过头部，则要把头部后面的数据发给远程服务端
                    if(strlen($buffer) > $header_len)
                    {
                        $remote_connection->send(substr($buffer,$header_len));
                    }
            }
        };


        // UDP support
        $worker_udp = new Worker('udp://0.0.0.0:'. $PORT);
        $worker_udp->count = 1;
        $worker_udp->name = 'shadowsocks-server';

        /*
         * todo: UDP部分暂时有一些问题
         */
        $worker_udp->onMessage = function($connection, $buffer)use($METHOD, $PASSWORD)
        {
            $encryptor = new Encryptor($PASSWORD, $METHOD, true);
            $buffer = $encryptor->decrypt($buffer);
            // 解析socket5头
            $header_data = Socks5::parse_socket5_header($buffer);
            // 解析头部出错，则关闭连接
            if(!$header_data)
            {
                $connection->close();
                return;
            }
            // 头部长度
            $header_len = $header_data[3];
            $host = $header_data[1];
            $port = $header_data[2];
            $address = "udp://$host:$port";

            $remote_connection = new AsyncUdpConnection($address);
            @$remote_connection->source = $connection;
            $remote_connection->onConnect = function($remote_connection)use($buffer, $header_len)
            {
                $remote_connection->send(substr($buffer,$header_len));
            };
            $remote_connection->onMessage = function($remote_connection, $buffer)use($header_data, $METHOD, $PASSWORD)
            {
                $_header = Socks5::pack_header($header_data[1], $header_data[0], $header_data[2]);
                $encryptor = new Encryptor($PASSWORD, $METHOD, true);
                $_data = $encryptor->encrypt($_header . $buffer);
                $remote_connection->source->send($_data);
            };
            $remote_connection->connect();
        };

        Worker::runAll();
        return self::SUCCESS;
    }
}
